package OpenToday;
import OpenToday.Configuration.ConfigurationForm;
import OpenToday.Configuration.OpenTodayConfiguration;
import OpenToday.GUI.*;
import OpenToday.PIMHelper.*;
import OpenToday.Tools.ExceptionUtils;
import OpenToday.Tools.TimeUtils;
import com.sun.kvem.midp.pim.PIMHandler;
import com.sun.kvem.midp.pim.PIMImpl;
import java.io.IOException;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.pim.*;
import java.util.*;

public class OpenTodayMain extends MIDlet implements CommandListener, IManager 
{
    //private Form mMainForm;
    private IOpenTodayGUI mMainGUI;
    private boolean m_alreadyStarted;
    private boolean m_paused;
    private RefresherThread m_refresherThread;
    private long m_eventsCheckSum;
    private boolean m_mustRefresh;
    private final boolean m_inEmulator;
    private final Stack m_displayStack;
    
    private static void ClearPIMForDebugging()
    {
        // Delete all events having to do with Christmas (bah humbug!)
        PIM pim = PIM.getInstance();
        EventList el = null;
        Enumeration xmas_events = null;
        try 
	{
            el = (EventList) pim.openPIMList(PIM.EVENT_LIST, PIM.READ_WRITE);
	    // Get the enumeration of matching elements
	    xmas_events = el.items();
	    // delete all event entries containing Christmas
	    while (xmas_events != null && xmas_events.hasMoreElements()) 
	    {
		Event e = (Event) xmas_events.nextElement();
		try 
		{
		    el.removeEvent(e);
		} 
		catch (PIMException exe) 
		{
		    // couldn't delete the entry for some reason
		    System.err.println("This application cannot remove the event info");
		}
	    }
	    el.close();
        } 
	catch (PIMException e) 
	{
            // failed opening the default event list!
            System.err.println("Error accessing database - aborting action");
        } 
	catch (SecurityException e) 
	{
            // user rejected application's request 
            // for read/write access to event list
            // This is not an error condition and can be normal
            System.out.println("Okay, this application won't modify any event");
        }
	try 
	{
            ToDoList tl = (ToDoList) pim.openPIMList(PIM.TODO_LIST, PIM.READ_WRITE);
	    // Get the enumeration of matching elements
	    Enumeration todos = tl.items();
	    // delete all event entries containing Christmas
	    while (todos != null && todos.hasMoreElements()) 
	    {
		ToDo t = (ToDo) todos.nextElement();
		try 
		{
		    tl.removeToDo(t);
		} 
		catch (PIMException exe) 
		{
		    // couldn't delete the entry for some reason
		    System.err.println("This application cannot remove the event info");
		}
	    }
	    tl.close();
        } 
	catch (PIMException e) 
	{
            // failed opening the default event list!
            System.err.println("Error accessing database - aborting action");
        } 
	catch (SecurityException e) 
	{
            // user rejected application's request 
            // for read/write access to event list
            // This is not an error condition and can be normal
            System.out.println("Okay, this application won't modify any event");
        }
    }
  
    private static void AddEventForDebugging(EventList el, long startTime, long endTime, String title) throws PIMException
    {
	Event new_event = el.createEvent();
	new_event.addDate(Event.START, PIMItem.ATTR_NONE, startTime);
	new_event.addDate(Event.END, PIMItem.ATTR_NONE, endTime);
	if (el.isSupportedField(Event.SUMMARY))
	    new_event.addString(Event.SUMMARY, PIMItem.ATTR_NONE, title);
	
	new_event.commit();
    }
    
    private static void AddToDoForDebugging(ToDoList tl, long dueTime, String title, boolean done) throws PIMException
    {
	ToDo new_todo = tl.createToDo();
	new_todo.addDate(ToDo.DUE, PIMItem.ATTR_NONE, dueTime);
	if (tl.isSupportedField(ToDo.SUMMARY))
	    new_todo.addString(ToDo.SUMMARY, PIMItem.ATTR_NONE, title);
	if (done)
	{
	    new_todo.addBoolean(ToDo.COMPLETED, PIMItem.ATTR_NONE, done);
	    new_todo.addDate(ToDo.COMPLETION_DATE, PIMItem.ATTR_NONE, dueTime - 1000);
	}
	new_todo.commit();
    }
    
    private static void AddEventsForDebugging()
    {
	// Create an event entry in the device's local calendar
        // reminding the user to register your application
        PIM pim = PIM.getInstance();
        EventList el = null;
        try 
	{
            // Open write only since you're just going to 
            //add your registration
            // event info to the device's database
            el = (EventList) pim.openPIMList(PIM.EVENT_LIST, PIM.WRITE_ONLY);
        } 
	catch (PIMException e) 
	{
            // failed opening the default event list!
            // Error case - abort this attempt
            System.err.println("Error accessing database - aborting action");
            return;
        } 
	catch (SecurityException e) {
            // user rejected application's request 
            // for write access to event list
            // This is not an error condition and can be normal
            System.out.println("Okay, this application won't add the event");
            return;
        }
        
	try 
	{
	    Date d = new Date();
	    long l = d.getTime() ;
	    AddEventForDebugging(el, l, l + (60 * 60 * 1000), "Now");
	    AddEventForDebugging(el, l + (60 * 60 * 1000), l + (120 * 60 * 1000), "����");
	    //long nextDay = TimeUtils.CalcualteRoundDate(l) + TimeUtils.MS_PER_DAY;
	    //AddEventForDebugging(el,  nextDay, nextDay + (23 * 60 * 1000), "Testing event for next day!");
	}
	catch (PIMException e) 
	{
            // failed opening the default event list!
            // Error case - abort this attempt
            System.err.println("Error adding events for debugging");
            return;
        } 
        try 
	{
            el.close();
        } catch (PIMException e) {
            // failed to close the list
        }
    }
    
    private static void AddTasksForDebugging()
    {
	PIM pim = PIM.getInstance();
        try 
	{
            ToDoList tl = (ToDoList) pim.openPIMList(PIM.TODO_LIST, PIM.WRITE_ONLY);
	    Date d = new Date();
	    long l = d.getTime() ;
	    AddToDoForDebugging(tl, l, "Task Now", false);
	    AddToDoForDebugging(tl, l, "Task Done Now", true);
	    long nextDay = TimeUtils.CalcualteRoundDate(l) + TimeUtils.MS_PER_DAY;
	    AddToDoForDebugging(tl, nextDay, "Task tomorrow", false);
	    AddToDoForDebugging(tl, nextDay, "Task Done tomorrow", true);
	    long yesterday = TimeUtils.CalcualteRoundDate(l) - TimeUtils.MS_PER_DAY;
	    AddToDoForDebugging(tl, yesterday, "Task yesterday", false);
	    AddToDoForDebugging(tl, yesterday, "Task Done yesterday", true);  
	    tl.close();
        } 
	catch (PIMException e) 
	{
            System.err.println("Error accessing database - aborting action");
        } 
	catch (SecurityException e) 
	{
            System.out.println("Okay, this application won't add the event");
        }
    }
    
    public OpenTodayMain() throws Exception 
    {
	System.out.println ("OpenTodayMain: ctor");
	m_eventsCheckSum = 0;
	m_mustRefresh = true;
	m_displayStack = new Stack();
	
	System.out.println ("OpenTodayMain: ctor - initializing OpenTodayConfiguration");
	OpenTodayConfiguration.getInstance().InitializeOpenTodayConfiguration(this);
	if (OpenTodayConfiguration.getInstance().get_TextBasedTodayScreen())
	    mMainGUI = new MainForm(this);
	else
	    mMainGUI = new MainCanvas(this);

	Displayable formForCommands = mMainGUI.getDisplayable();
	//about command
	formForCommands.addCommand(new Command("About", Command.SCREEN, 0));
	formForCommands.setCommandListener(this);
	//exit command
	formForCommands.addCommand(new Command("Exit", Command.EXIT, 0));
	formForCommands.setCommandListener(this);
	//config command
	formForCommands.addCommand(new Command("Config", Command.HELP, 1));
	formForCommands.setCommandListener(this);
	//background command
	formForCommands.addCommand(new Command("Minimize", Command.STOP, 2));
	formForCommands.setCommandListener(this);
	
	String details =  System.getProperty("microedition.platform");

	if  ("SonyEricssonK600/JAVASDK".equalsIgnoreCase(details) ||
		"SonyEricssonW800/JAVASDK".equalsIgnoreCase(details) ||
		"SunMicrosystems_wtk".equalsIgnoreCase(details))
	{//hopefully this code will only run in my emulator. But DO NOT FOR GET TO REMOVE FROM CODE BEFORE DEPLOYMENT!
	    //ClearPIMForDebugging();
	    //AddEventsForDebugging();
	    //AddTasksForDebugging();
	    m_inEmulator = true;
	}
	else
	    m_inEmulator = false;
 
	m_alreadyStarted=false;
	m_paused=false;
	
	//OK, maybe this will fix the application dead-lock.
	//i'll wait till the image is ready (if one was selected) the  GetLoadedBackgroundImage function will wait till it is loaded
	System.out.println ("OpenTodayMain: ctor - Checking loaded background image...");
	if (OpenTodayConfiguration.getInstance().GetLoadedBackgroundImage() ==null)
	    System.out.println ("OpenTodayMain: ctor - no background image loaded.");
	else
	    System.out.println ("OpenTodayMain: ctor - background image loaded.");

	//starting refresher thread
	m_refresherThread = new RefresherThread(this);
    }
    
    public void commandAction(Command command, Displayable displayable)
    {
	 if (command.getCommandType() == Command.EXIT)
	 {
	     System.out.println ("OnExitPressed");
	    destroyApp(true);
	    //notifyDestroyed();
	 }
	 else if (command.getCommandType() == Command.HELP)
	 {
	     synchronized(mMainGUI)
	    {
		System.out.println ("OnConfigurePressed");
		//load config form
		ConfigurationForm frm = new ConfigurationForm(this);
		OnNewDisplayable(frm);
	    }
	 }
	 else if (command.getCommandType() == Command.SCREEN)
	 {   
	     synchronized(mMainGUI)
	    {
		System.out.println ("OnAboutPressed");
		//load config form
		AboutForm frm = new AboutForm(this, this);
		OnNewDisplayable(frm);
	    }
	 }
	 else if (command.getCommandType() == Command.STOP)
	 {   
	     System.out.println ("OnMinimizePressed");
	     Display.getDisplay(this).setCurrent(null);
	 }
    }
  
    public void startApp () 
    {
        synchronized(mMainGUI)
        {
            System.out.println ("startApp");
            if (!m_alreadyStarted) 
            {
                m_alreadyStarted = true;
                Display.getDisplay(this).setCurrent(mMainGUI.getDisplayable());
                m_refresherThread.Start();
            }
        }
    }
    
    public void pauseApp () 
    {
	synchronized(mMainGUI)
        {
            System.out.println ("pauseApp");
            if (!m_paused)
            {
                m_paused = true;
                m_refresherThread.PauseRefreshing();
            } 
        }
    }
    
    public void resumeApp()
    {
        synchronized(mMainGUI)
        {
            System.out.println ("resumeApp");
            if (m_paused)
            {
                m_mustRefresh = true;//after resuming we must do repaint
                m_paused = false;
                m_refresherThread.ResumeRefreshing();
            }
        }
    } 

    public void OnBackPressed()
    {
	synchronized(mMainGUI)
        {
	    System.out.println ("OnBackPressed");
	    if (m_displayStack.size() > 0) m_displayStack.pop();
            if (m_displayStack.size() == 0)
	    {
		
		Display.getDisplay(this).setCurrent(mMainGUI.getDisplayable());
		//and refresh the screen
		System.out.println("Must refresh - showing the today screen");
		m_mustRefresh = true;//after changing the configuration, we must do repaint
		m_refresherThread.ResumeRefreshing();
	    }
	    else
	    {
		Display.getDisplay(this).setCurrent((Displayable)m_displayStack.peek());
	    }
        }
    }
    
    public void OnNewDisplayable(Displayable displayable)
    {
	System.out.println ("OnNewDisplayable");
	m_displayStack.push(displayable);
	Display.getDisplay(this).setCurrent(displayable);
    }
    
    public void destroyApp (boolean unconditional) 
    {
	System.out.println ("destroyApp");
	m_refresherThread.Stop();
	try
	{
	    PIMHelper.getInstance().Destroy();
	    OpenTodayConfiguration.getInstance().Shutdown();
	} 
	catch (PIMException ex)
	{
	    ex.printStackTrace();
	}
	finally
	{
	    super.notifyDestroyed();
	}
    }
    
    public void RefreshTodayScreen()
    {
	synchronized(mMainGUI)
        {
            System.out.println ("RefreshTodayScreen");
	    ExceptionUtils.PHASE("start");
            try 
            {
		ExceptionUtils.PHASE("get PIM");
		PIMHelper pim = PIMHelper.getInstance();
		ExceptionUtils.PHASE("get all events");
                IPhoneCalendarEvents calcualtedEvents = pim.createCurrentCalendarStatus();
		ExceptionUtils.PHASE("checksum");
                if ((!m_mustRefresh) && (m_eventsCheckSum == calcualtedEvents.getEventsCheckSum()))
                {
                    System.out.println ("Checksum equals. " + m_eventsCheckSum);
                    return;//same, no need to refresh
                }
                else
                {
		    OpenTodayConfiguration cnf = OpenTodayConfiguration.getInstance();
		    try
		    {
			ExceptionUtils.PHASE("refresh");
			m_mustRefresh = false;
			//first to remove anything we have now
			mMainGUI.SetFirstLineOffset(OpenTodayConfiguration.getInstance().get_ScreenTopOffset());
			mMainGUI.Clear();
			//rebuilding screen
			ExceptionUtils.PHASE("start rebuild screen");

			if (calcualtedEvents.getHasOverdueItems() && cnf.getShowOverdueItems())
			{
			    mMainGUI.AddDayLine(cnf.getOverdueItemsTitle());
			    IPhoneCalendarDayEvents overdueItems = calcualtedEvents.getEventsForDay(calcualtedEvents.getOverdueDayIndex());
			    for(int tasksIndex=0; tasksIndex<overdueItems.getToDosCount(); tasksIndex++)
			    {
				IPhoneToDo todo = overdueItems.getToDoAtIndex(tasksIndex);
				mMainGUI.AddToDoLine(true, todo.isDone(), todo.getToDoTitle());
			    }
			}
			boolean shouldDisplayNothingText = true;
			//today string
			if (calcualtedEvents.getHasEventsToday())
			    mMainGUI.AddDayLine("Today");
			else if (cnf.getShowTodayAlways())
			{
			    mMainGUI.AddDayLine("Today: " + cnf.get_NoEventsText());
			    //once is enough
			    shouldDisplayNothingText = false;
			}
			
			//populating
			int days = calcualtedEvents.getMaximumDayIndex();
			ExceptionUtils.PHASE("start days loop. has "+days);
			for(int dayIndex=0; dayIndex < days; dayIndex++)
			{
			    ExceptionUtils.PHASE("day " + dayIndex);
			    IPhoneCalendarDayEvents dayEvents = calcualtedEvents.getEventsForDay(dayIndex);
			    if ((dayEvents.getEventsCount() + dayEvents.getToDosCount()) == 0)
				continue;//nothing this day, skipping
			    //if we got here, then there is atleast one day with events
			    shouldDisplayNothingText = false;
			    
			    if (dayEvents.getDayOffset() > 0)//no the special today (you know, special day title, nothing today)
				mMainGUI.AddDayLine(CreateDayString(dayEvents));
			    //todos
			    ExceptionUtils.PHASE("start todos loop. has "+dayEvents.getToDosCount());
			    for(int todoIndex=0; todoIndex<dayEvents.getToDosCount();todoIndex++)
			    {
				IPhoneToDo todo = dayEvents.getToDoAtIndex(todoIndex);
				ExceptionUtils.PHASE("todo " + todoIndex);
				mMainGUI.AddToDoLine(false, todo.isDone(), todo.getToDoTitle());
			    }
			    //events
			    ExceptionUtils.PHASE("start events loop. has "+dayEvents.getEventsCount());
			    for(int eventIndex=0; eventIndex<dayEvents.getEventsCount();eventIndex++)
			    {
				IPhoneCalendarEvent event = dayEvents.getEventAtIndex(eventIndex);
				ExceptionUtils.PHASE("event " + eventIndex);
				//long allTime = 
				if (event.isAllDayEvent())
				{
				    ExceptionUtils.PHASE("all day event: "+event.getEventTitle());				
				    mMainGUI.AddAllDayEventLine(event.getEventTitle());
				}
				else
				{
				    ExceptionUtils.PHASE("a day event: "+event.getEventTitle());
                                    String timeString = TimeUtils.CreateSimpleTimeString(event.getEventStartTime());
                                    if (OpenTodayConfiguration.getInstance().getShowEndTime())
                                    {
                                        String endTimeString = TimeUtils.CreateSimpleTimeString(event.getEventEndTime());
                                        if (!endTimeString.equals(timeString))
                                        {
                                            timeString += "-";
                                            timeString += endTimeString;
                                        }
                                    }
				    mMainGUI.AddEventLine(event.isCurrentEvent(), timeString, event.getEventTitle());
				}
			    }
			}
			//should we print the nothing text?
			if (shouldDisplayNothingText)
			{
			    mMainGUI.AddDayLine(cnf.get_NoEventsText());
			}
		    }
		    finally
		    {
			mMainGUI.RepaintRequired();//making sure the GUI is repainted
		    }
		}
            } 
            catch (Exception e) 
            {
                // failed to retrieve elements due to error!
                //adding to GUI
		e.printStackTrace();
                Alert error = new Alert("Error", "Error while refreshing today screen:\n\rPhase:"+ExceptionUtils.getPhases()+"\n\r"+e.toString(), null, AlertType.ERROR);
                error.setTimeout(Alert.FOREVER);
                Display.getDisplay(this).setCurrent(error);
            }
        }
    }

    private String CreateDayString(IPhoneCalendarDayEvents dayEvents)
    {
	// Format the current time.
	if (dayEvents.getDayOffset() == 0)
	    return "Today";
	else if (dayEvents.getDayOffset() == 1)
	    return "Tomorrow";
	else
	    return dayEvents.getDayOffset() + " days ahead";
    }

    public void OnError(String errorString) 
    {
	if (!OpenTodayConfiguration.getInstance().getDiscardErrors())
	{
	    synchronized(mMainGUI)
	    {
		Alert error = new Alert("Error", "Error encounterred:\n\r"+errorString, null, AlertType.ERROR);
		error.setTimeout(Alert.FOREVER);
		Display.getDisplay(this).setCurrent(error);
	    }
	}
    }
    
    public void OnMessage(String message) 
    {
        synchronized(mMainGUI)
        {
            Alert msg = new Alert("Message", message, null, AlertType.INFO);
            msg.setTimeout(Alert.FOREVER);
            Display.getDisplay(this).setCurrent(msg);
        }
    }
}